// including external libraries
#include "Material.hpp"
#include <iostream>
#include <cstdlib>
#include "math.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/rotate_vector.hpp>

// including internal headers
#include "Shader.h"
#include "utils.hpp"
#include "Camera.hpp"
#include "Entity.hpp"
#include "Node.hpp"

using namespace std;

#ifndef __OBJECT_HPP__
#define __OBJECT_HPP__


/** Class used for modification notification in order to avoid using
  * active detection loop */
class ModificationListener {
	protected:
		/** position of this ModificationListener in the binded Object container */
		list<ModificationListener*>::iterator m_iterator;
	public:
		/** virtual function that has to be overloaded by Child class, each time the object
		  * binded with this ModificationListener will suffer a modification, this fonction 
		  * will be called */
		virtual void modificationUpdate() = 0;

		/** set ModificationListener iterator */
		void setModificationIterator(list<ModificationListener*>::iterator mli_) { m_iterator = mli_;};
		
		/** return ModificationListener iterator */
		list<ModificationListener*>::iterator getModificationIterator() { return m_iterator;};		
};


class Object {
  public:
	/** Object iterator : position in ObjectListener container */
    list<Object*>::iterator m_iterator;

	/** list of Modification listener, that will be notified each time a property modification happens */
	list<ModificationListener*> m_modificationListenerList;

	/** main node of the Object, dictates the Object position, main entity ... */
    Node* m_mainNode;

	/** Object name */
	string m_name;

	/** Object Global HitBoxes, used for collision and selection detection */
	HitBoxes m_hitboxes;
  public:
	/** defined the object trajectory, this trajectory is a beziez curve derived
	  * from point arguments */ 
	virtual void setTrajectory(vec3 P0, vec3 P1, vec3 P2, vec3 P3){ return;};

	/** link a new modification listener to this Object, each time a modification happens
	  * on some Object properties, the listener will be notified*/	
	void addModificationListener(ModificationListener* ml_);

	/** unlink a modification listener from the Object */
	void removeModificationListener(ModificationListener* ml_) {
		m_modificationListenerList.erase(ml_->getModificationIterator());
	};

	/** function to be called each time information change (life ...)  in order
	 * to update dependant elements (interface window ...) 
	 */
	virtual void updateModificationListener() {
		list<ModificationListener*>::iterator it = m_modificationListenerList.begin();
		for (; it != m_modificationListenerList.end(); it++) (*it)->modificationUpdate();
		return;
	};

	/** set Object name */
	void setName(string name_) { m_name = name_;};

	/** return object name */
	string getName() { return m_name;};

	/** attach object to ObjectGestionner */
    void attach();

	/** DEPRECATED */
    void erase();

	/** update object throught updating main Node, using time argument to determine
	  * movement ... since last call */
    virtual bool update(float time) { m_mainNode->update(time); return true;};//= 0;

	/** function for rendering object on screen */
    virtual void render() { m_mainNode->render();};

	/** function to send a command to the object via the string s */
	virtual void action(int action_) { return; };

	/** defined the main node of the object */
	void setNode(Node* node_) { m_mainNode = node_;};

	/** return a pointer toward the main object Node */
    Node* getNode() { return m_mainNode;};

	/** defined the material that will be used for rendering the object (entity of the main node) */
	void setMaterial(Material* material_) { m_mainNode->getEntity()->setMaterial(material_);};

	/** retrieve the Object Iterator in the ObjectGestionner list, used when deleting the Object */
	list<Object*>::iterator getIterator() { return m_iterator;};

	/** set the Object Iterator, used by ObjectGestionner to store Object position in list */
	void setIterator(list<Object*>::iterator it) { m_iterator = it; };

	/** return the current object position, directly derived from the main node position */ 
	vec3 getPosition() {
		return m_mainNode->getPosition();
	}

	/** return the direction vector of the object (derived from the main Node direction */
	vec3 getDirection();

	/** compute the matrix that indicates the current object position */
    glm::mat4 getPositionMatrix();

	/** return the object HitBoxes */
    HitBoxes getHitBoxes() {
		return m_hitboxes;
	};

	/** return the Object Selection : convex envelopp of the hitboxes screen projection
      *  can be used to decide if a mouse click select this object or not */
	Selection* getSelection() {
		return m_hitboxes.getSelection(getPositionMatrix());
	};

	/** If the object is selected this function will be called */
	virtual void select() { return; };

	/** call while the object is unselected (for example if he was wselected, and that an other object is selected) */
	virtual void unselect() { return; };
    
};

class ObjectGestionner {
  protected:
    static list<Object*> m_objectList;
    ObjectGestionner() {};
  public:
    static void createInstance();
    static list<Object*>::iterator attachObject(Object* node) {
      return m_objectList.insert(m_objectList.end(),node);
    };
    static void detachObject(list<Object*>::iterator it) { m_objectList.erase(it);};

	static list<Object*>::iterator detachObject(Object* obj) { return m_objectList.erase(obj->getIterator());};

    static void update(float time) {
      for (list<Object*>::iterator it = m_objectList.begin(); it != m_objectList.end();) 
        if (!(*it)->update(time)) {
	  Object* p = *it;
	  it = detachObject(p);//m_objectList.erase(p->m_iterator);
	  delete p;
	} else it++;
    }
    static void render() {
      for (list<Object*>::iterator it = m_objectList.begin(); it != m_objectList.end(); it++) {
        glLoadIdentity();
        (*it)->render();
      }
    }
	static Object* getSelected(vec2 point) {
      for (list<Object*>::iterator it = m_objectList.begin(); it != m_objectList.end();it++) { 
			Selection* sel = (*it)->getHitBoxes().getSelection((*it)->getPositionMatrix());
			if (sel->intersect(point)) {
				delete sel;
				cout << "new selection" << endl;
				return (*it);
			}
		};
		return NULL;
	};
};

#endif
